package com.radar.visualize;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.amazonaws.util.IOUtils;
import com.amazonaws.util.StringUtils;
import com.radar.algorithm.MathCommon;
import com.radar.common.Common;
import com.radar.common.DateConstants;
import com.radar.common.GisConstants;
import com.radar.common.ProductType;
import com.radar.exception.RadarCaclError;
import com.radar.exception.RadarVisualizeError;
import com.radar.io.ShapeSourcePool;
import com.radar.io.cinrad.BaseCinrad;
import org.meteoinfo.data.mapdata.ShapeFileManage;
import wcontour.global.PointD;

import java.awt.*;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.*;
import java.util.List;
import java.util.stream.IntStream;

/**
 * @author: fy-wbj
 * @description: draw polar
 * @date: 2020/9/26
 */
public class PolarMap extends ThematicMap {

    public PolarMap(BaseCinrad radar) {
        super(radar);
    }

    //region polar

    /**
     * @author: fy-wbj
     * @description: draw polar radar info
     * @date: 2020/9/22
     */
    public static class PolarInfo {

        /**
         * radar elevation index
         */
        private int layers;

        /**
         * radar element
         */
        private ProductType type;

        /**
         * polar rings  (if null ? do not draw a circle : max range is rings[rings.length - 1])
         */
        private int[] rings = null;

        /**
         * if null ? Default background color blacs
         */
        private Color backColor = Color.black;

        /**
         * draw radar site detail on right
         */
        private boolean simpleDetail = false;

        public PolarInfo rings(int[] rings) throws RadarVisualizeError {
            try {
                if (rings == null || rings.length == 0) {
                    throw new RadarVisualizeError("rings length must > 0");
                }
                if (rings.length > 2) {
                    for (int i = 1; i < rings.length; i++) {
                        if (rings[i] <= rings[i - 1]) {
                            throw new RadarVisualizeError("set rings error");
                        }
                    }
                }
            } catch (Exception e) {
                throw new RadarVisualizeError("set rings error");
            }
            this.rings = rings;
            return this;
        }

        public PolarInfo backColor(Color backColor) {
            this.backColor = backColor;
            return this;
        }

        public PolarInfo simpleDetail(boolean simpleDetail) {
            this.simpleDetail = simpleDetail;
            return this;
        }
    }

    public static PolarInfo buildPolarInfo(int layers, ProductType type) {
        PolarInfo polarInfo = new PolarInfo();
        polarInfo.layers = layers;
        polarInfo.type = type;
        return polarInfo;
    }

    //TODO 优化效果
    public BufferedImage simplePolar(PolarInfo polarInfo) throws RadarCaclError, RadarVisualizeError {
        double[][] raw = MathCommon.decode(radar.getRaw(polarInfo.layers, polarInfo.type), polarInfo.type);
        double[] azimuth = radar.getAzimuth()[polarInfo.layers];
        double resolution = radar.getResolution(polarInfo.type) / 1000.0;
        boolean rmax = polarInfo.rings != null;

        int width = rmax ? (int) (polarInfo.rings[polarInfo.rings.length - 1] * 2 / resolution) : raw[0].length * 2;
        int height = rmax ? (int) (polarInfo.rings[polarInfo.rings.length - 1] * 2 / resolution) : raw[0].length * 2;

        int xOffset = 0;
        int yOffset = 0;

        int x0 = (int) (width / 2f);
        int y0 = (int) (height / 2f);

        int rwidth = width;
        if (polarInfo.simpleDetail) {
            width += width / 4;
        }
        BufferedImage br = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = br.createGraphics();
        try {
            double deltaD = 0.5;
            double deltaA = 1;
            g.setBackground(polarInfo.backColor);
            g.clearRect(0, 0, width, height);
            g.setPaint(polarInfo.backColor);
            g.drawOval(0, 0, width, height);
            for (int i = 0; i < raw.length; i++) {
                for (int j = 0; j < raw[0].length; j++) {
                    if (raw[i][j] != GisConstants.UNDEF) {
                        Color color = getColor(raw[i][j], polarInfo.type);
                        g.setColor(color);
                        PointD[] p = new PointD[]{new PointD(), new PointD(), new PointD(), new PointD()};
                        double a0 = azimuth[i];
                        double a01;
                        double a02;
                        if (i == 0) {
                            a01 = azimuth[azimuth.length - 1];
                            a02 = azimuth[1];
                        } else if (i == raw.length - 1) {
                            a01 = azimuth[azimuth.length - 2];
                            a02 = azimuth[0];
                        } else {
                            a01 = azimuth[i - 1];
                            a02 = azimuth[i + 1];
                        }
                        if (a0 == GisConstants.UNDEF || a01 == GisConstants.UNDEF || a02 == GisConstants.UNDEF) {
                            continue;
                        }
                        double d1 = Math.abs(a0 - a01) / 2;
                        double d2 = Math.abs(a0 - a02) / 2;
                        if (d1 > deltaA) {
                            d1 = deltaA;
                        }
                        if (d2 > deltaA) {
                            d2 = deltaA;
                        }
                        double a1 = (a0 + d1) * Math.PI / 180;
                        double a2 = (a0 - d2) * Math.PI / 180;
                        p[0].X = (Math.sin(a1) * (j - deltaD + xOffset));
                        p[0].Y = (Math.cos(a1) * (j - deltaD + yOffset));
                        p[1].X = (Math.sin(a1) * (j + deltaD + xOffset));
                        p[1].Y = (Math.cos(a1) * (j + deltaD + yOffset));
                        p[2].X = (Math.sin(a2) * (j + deltaD + xOffset));
                        p[2].Y = (Math.cos(a2) * (j + deltaD + yOffset));
                        p[3].X = (Math.sin(a2) * (j - deltaD + xOffset));
                        p[3].Y = (Math.cos(a2) * (j - deltaD + yOffset));
//                        Polygon poly = new Polygon();
                        Path2D path2D = new Path2D.Double();
                        path2D.moveTo((p[0].X + x0), (height - (p[0].Y + y0)));
                        path2D.lineTo((p[1].X + x0), (height - (p[1].Y + y0)));
                        path2D.lineTo((p[2].X + x0), (height - (p[2].Y + y0)));
                        path2D.lineTo((p[3].X + x0), (height - (p[3].Y + y0)));
                        path2D.lineTo((p[0].X + x0), (height - (p[0].Y + y0)));
                        g.draw(path2D);
                        g.fill(path2D);
//                        poly.addPoint((int) (p[0].X + x0), (int) (height - (p[0].Y + y0)));
//                        poly.addPoint((int) (p[1].X + x0), (int) (height - (p[1].Y + y0)));
//                        poly.addPoint((int) (p[2].X + x0), (int) (height - (p[2].Y + y0)));
//                        poly.addPoint((int) (p[3].X + x0), (int) (height - (p[3].Y + y0)));
//                        g.drawPolygon(poly);
//                        g.fillPolygon(poly);
                    }
                }
            }
            Font font = new Font("Arial", Font.PLAIN, 12);
            g.setFont(font);
            g.setColor(new Color(255 - polarInfo.backColor.getRed(), 255 - polarInfo.backColor.getGreen(), 255 - polarInfo.backColor.getBlue(), 128));
            // draw rings
            if (polarInfo.rings != null) {
                for (int i = 0; i < polarInfo.rings.length; i++) {
                    Stroke dash = new BasicStroke(1.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 2.5f, new float[]{10, 5,}, 0f);
                    g.setStroke(dash);
                    int z = (int) (polarInfo.rings[i] / resolution);
                    g.drawArc(x0 - z + xOffset, y0 - z + yOffset, z * 2, z * 2, 0, 360);
                    g.drawString(String.format("%d km", polarInfo.rings[i]), x0 + xOffset, y0 - z + yOffset);
                }
            }
            // draw simpleDetail
            if (polarInfo.simpleDetail) {
                g.setColor(new Color(255 - polarInfo.backColor.getRed(), 255 - polarInfo.backColor.getGreen(), 255 - polarInfo.backColor.getBlue(), 200));
                int x = rwidth + 20;
                int y = 30;
                int h = height / 4;
                int zh = h / 10;
                font = new Font("Arial", Font.PLAIN, zh);
                g.setFont(font);
                g.drawString(polarInfo.type.toString(), x, y);
                y += zh;
                g.drawString(String.format("Range: %s km", rmax ? (int) (polarInfo.rings[polarInfo.rings.length - 1] * 2 / resolution) : raw[0].length * 2), x, y);
                y += zh;
                g.drawString(String.format("Resolution: %s km", resolution), x, y);
                y += zh;
                g.drawString(String.format("Date: %s", DateUtil.format(radar.dateTime, DateConstants.DATE_FORMAT_YYYY_MM_DD)), x, y);
                y += zh;
                g.drawString(String.format("Time: %s", DateUtil.format(radar.dateTime, "HH:mm")), x, y);
                y += zh;
                g.drawString(String.format("Radr: %s", radar.getRadarSite().getStationName()), x, y);
                y += zh;
                g.drawString(String.format("Task: VCP %s ", radar.vcp), x, y);
                y += zh;
                g.drawString(String.format("Evel: %s deg ", simpleDoubleText(radar.getElevation()[polarInfo.layers])), x, y);
                y += zh;
                g.drawString(String.format("Lon: %s ", simpleDoubleText(radar.getRadarSite().getLon())), x, y);
                y += zh;
                g.drawString(String.format("Lat: %s ", simpleDoubleText(radar.getRadarSite().getLat())), x, y);
                y += zh;
                g.drawString(String.format("Alt: %s m", simpleDoubleText(radar.getRadarSite().getAlt())), x, y);
                y += zh;
                drawLegend(g, polarInfo.type, x, height / 2, 30, br, new Color(255 - polarInfo.backColor.getRed(), 255 - polarInfo.backColor.getGreen(), 255 - polarInfo.backColor.getBlue(), 200));
            }
        } catch (Exception e) {
            throw new RadarVisualizeError(e.getMessage());
        } finally {
            if (g != null) {
                g.dispose();
            }
        }
        return br;
    }

    public BufferedImage simplePolarProduct(PolarInfo polarInfo, double[][] raw, double[] azimuth) throws RadarCaclError, RadarVisualizeError {
        int width = raw[0].length * 2;
        int height = raw[0].length * 2;

        int xOffset = 0;
        int yOffset = 0;

        int x0 = (int) (width / 2f);
        int y0 = (int) (height / 2f);
        BufferedImage br = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = br.createGraphics();
        try {
            double deltaD = 0.5;
            double deltaA = 1;
            g.setBackground(polarInfo.backColor);
            g.clearRect(0, 0, width, height);
            g.setPaint(polarInfo.backColor);
            g.drawOval(0, 0, width, height);
            for (int i = 0; i < raw.length; i++) {
                for (int j = 0; j < raw[0].length; j++) {
                    if (raw[i][j] != GisConstants.UNDEF) {
                        Color color = getColor(raw[i][j], polarInfo.type);
                        if (color == null) {
                            continue;
                        }
                        g.setColor(color);
                        PointD[] p = new PointD[]{new PointD(), new PointD(), new PointD(), new PointD()};
                        double a0 = azimuth[i];
                        double a01;
                        double a02;
                        if (i == 0) {
                            a01 = azimuth[azimuth.length - 1];
                            a02 = azimuth[1];
                        } else if (i == raw.length - 1) {
                            a01 = azimuth[azimuth.length - 2];
                            a02 = azimuth[0];
                        } else {
                            a01 = azimuth[i - 1];
                            a02 = azimuth[i + 1];
                        }
                        if (a0 == GisConstants.UNDEF || a01 == GisConstants.UNDEF || a02 == GisConstants.UNDEF) {
                            continue;
                        }
                        double d1 = Math.abs(a0 - a01) / 2;
                        double d2 = Math.abs(a0 - a02) / 2;
                        if (d1 > deltaA) {
                            d1 = deltaA;
                        }
                        if (d2 > deltaA) {
                            d2 = deltaA;
                        }
                        double a1 = (a0 + d1) * Math.PI / 180;
                        double a2 = (a0 - d2) * Math.PI / 180;
                        p[0].X = (Math.sin(a1) * (j - deltaD + xOffset));
                        p[0].Y = (Math.cos(a1) * (j - deltaD + yOffset));
                        p[1].X = (Math.sin(a1) * (j + deltaD + xOffset));
                        p[1].Y = (Math.cos(a1) * (j + deltaD + yOffset));
                        p[2].X = (Math.sin(a2) * (j + deltaD + xOffset));
                        p[2].Y = (Math.cos(a2) * (j + deltaD + yOffset));
                        p[3].X = (Math.sin(a2) * (j - deltaD + xOffset));
                        p[3].Y = (Math.cos(a2) * (j - deltaD + yOffset));
                        Path2D path2D = new Path2D.Double();
                        path2D.moveTo((p[0].X + x0), (height - (p[0].Y + y0)));
                        path2D.lineTo((p[1].X + x0), (height - (p[1].Y + y0)));
                        path2D.lineTo((p[2].X + x0), (height - (p[2].Y + y0)));
                        path2D.lineTo((p[3].X + x0), (height - (p[3].Y + y0)));
                        path2D.lineTo((p[0].X + x0), (height - (p[0].Y + y0)));
                        g.draw(path2D);
                        g.fill(path2D);
                    }
                }
            }
        } catch (Exception e) {
            throw new RadarVisualizeError(e.getMessage());
        } finally {
            if (g != null) {
                g.dispose();
            }
        }
        return br;
    }

    public BufferedImage simplePolarProduct346(PolarInfo polarInfo, double[][] raw, double[] azimuth, String colorcode) throws RadarCaclError, RadarVisualizeError {
        int width = raw[0].length * 2;
        int height = raw[0].length * 2;
        int x0 = (int) (width / 2f);
        int y0 = (int) (height / 2f);
        BufferedImage br = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = br.createGraphics();

        try (InputStream stream = ShapeSourcePool.class.getClassLoader().getResourceAsStream("color346" + File.separator + colorcode + ".cT")) {
            byte[] buffer = new byte[stream.available()];
            stream.read(buffer);
            String content = new String(buffer, Charset.defaultCharset());
            String[] colorstr = StrUtil.split(content.trim(), "\n");
            Color[] Colors = new Color[colorstr.length];
            IntStream.range(0, Colors.length).forEach(i -> {
                int[] s = StrUtil.splitToInt(colorstr[i], " ");
                Colors[i] = new Color(s[0], s[1], s[2], s[3]);
            });
            g.setBackground(polarInfo.backColor);
            g.clearRect(0, 0, width, height);
            g.setPaint(polarInfo.backColor);
            g.drawOval(0, 0, width, height);
            int w = raw[0].length * 2 + 1;
            int _nRangeBin = raw[0].length;
            int radials_num = raw.length;
            double tmp = radials_num / (Math.PI * 2.0);
            Map<Integer, List<Vector<Integer>>> mpRadial = new HashMap<>();
            for (int y = 0; y < w; y++) {
                y0 = _nRangeBin - y;
                int y2 = y0 * y0;
                for (int x = 0; x < w; x++) {
                    x0 = x - _nRangeBin;
                    int x2 = x0 * x0;
                    int r = (int) Math.sqrt(1.0 * x2 + 1.0 * y2);
                    if (r < _nRangeBin) {
                        double _index = Math.atan2(1.0 * x0, 1.0 * y0) * tmp;
                        int _radial_index = (int) _index;
                        if (_index < 0) {
                            _radial_index = (int) (radials_num + _index);
                        } else if (_index >= radials_num) {
                            _radial_index = 0;
                        }
                        Vector<Integer> v = new Vector<>();
                        v.add(x);
                        v.add(y);
                        v.add(r);
                        if (!mpRadial.containsKey(_radial_index)) {
                            mpRadial.put(_radial_index, new ArrayList<>());
                        }
                        mpRadial.get(_radial_index).add(v);
                    }
                }
            }
            double[] raster_data = new double[w * w];
            for (int i = 0; i < radials_num; i++) {
                for (int j = 0; j < mpRadial.get(i).size(); j++) {
                    int x = mpRadial.get(i).get(j).get(0);
                    int y = mpRadial.get(i).get(j).get(1);
                    int r = mpRadial.get(i).get(j).get(2);
                    raster_data[y * (2 * _nRangeBin + 1) + x] = raw[i][r];
                }
            }
            int index = 0;
            double[][] rdata = new double[w][w];
            for (int i = 0; i < w; i++) {
                for (int j = 0; j < w; j++) {
                    rdata[i][j] = raster_data[index++];
                }
            }
            return new RasterMap(null).drawSingleRaster(rdata, colorcode, ThematicMap.nullColor);
//            for (int i = 0; i < raw.length; i++) {
//                for (int j = 0; j < raw[0].length; j++) {
//                    if (raw[i][j] != GisConstants.UNDEF && raw[i][j] > 0 && raw[i][j] < Colors.length - 1) {
//                        g.setColor(Colors[(int) raw[i][j]]);
//
//                        PointD[] p = new PointD[]{new PointD(), new PointD(), new PointD(), new PointD()};
//                        if (i == raw.length - 1) {
//                            continue;
//                        }
//                        double a1 = (azimuth[i]) * Math.PI / 180;
//                        double a2 = (azimuth[i + 1]) * Math.PI / 180;
//
//                        p[0].X = (Math.sin(a1) * (j));
//                        p[0].Y = (Math.cos(a1) * (j));
//
//                        p[1].X = (Math.sin(a1) * (j + 1));
//                        p[1].Y = (Math.cos(a1) * (j + 1));
//
//                        p[2].X = (Math.sin(a2) * (j + 1));
//                        p[2].Y = (Math.cos(a2) * (j + 1));
//
//                        p[3].X = (Math.sin(a2) * (j));
//                        p[3].Y = (Math.cos(a2) * (j));
//
//                        Path2D path2D = new Path2D.Double();
//                        path2D.moveTo((p[0].X + x0), (height - (p[0].Y + y0)));
//                        path2D.lineTo((p[1].X + x0), (height - (p[1].Y + y0)));
//                        path2D.lineTo((p[2].X + x0), (height - (p[2].Y + y0)));
//                        path2D.lineTo((p[3].X + x0), (height - (p[3].Y + y0)));
//                        path2D.lineTo((p[0].X + x0), (height - (p[0].Y + y0)));
//                        g.draw(path2D);
//                        g.fill(path2D);
//                    }


//                    if (raw[i][j] != GisConstants.UNDEF && raw[i][j] > 0 && raw[i][j] < Colors.length - 1) {
//                        g.setColor(Colors[(int) raw[i][j]]);
//                        PointD[] p = new PointD[]{new PointD(), new PointD(), new PointD(), new PointD()};
//                        double a0 = azimuth[i];
//                        double a01;
//                        double a02;
//                        if (i == 0) {
//                            a01 = azimuth[azimuth.length - 1];
//                            a02 = azimuth[1];
//                        } else if (i == raw.length - 1) {
//                            a01 = azimuth[azimuth.length - 2];
//                            a02 = azimuth[0];
//                        } else {
//                            a01 = azimuth[i - 1];
//                            a02 = azimuth[i + 1];
//                        }
//                        if (a0 == GisConstants.UNDEF || a01 == GisConstants.UNDEF || a02 == GisConstants.UNDEF) {
//                            continue;
//                        }
//                        double d1 = Math.abs(a0 - a01) / 2;
//                        double d2 = Math.abs(a0 - a02) / 2;
//                        if (d1 > deltaA) {
//                            d1 = deltaA;
//                        }
//                        if (d2 > deltaA) {
//                            d2 = deltaA;
//                        }
//                        double a1 = (a0 + d1) * Math.PI / 180;
//                        double a2 = (a0 - d2) * Math.PI / 180;
//                        p[0].X = (Math.sin(a1) * (j - deltaD + xOffset));
//                        p[0].Y = (Math.cos(a1) * (j - deltaD + yOffset));
//                        p[1].X = (Math.sin(a1) * (j + deltaD + xOffset));
//                        p[1].Y = (Math.cos(a1) * (j + deltaD + yOffset));
//                        p[2].X = (Math.sin(a2) * (j + deltaD + xOffset));
//                        p[2].Y = (Math.cos(a2) * (j + deltaD + yOffset));
//                        p[3].X = (Math.sin(a2) * (j - deltaD + xOffset));
//                        p[3].Y = (Math.cos(a2) * (j - deltaD + yOffset));
//                        Path2D path2D = new Path2D.Double();
//                        path2D.moveTo((p[0].X + x0), (height - (p[0].Y + y0)));
//                        path2D.lineTo((p[1].X + x0), (height - (p[1].Y + y0)));
//                        path2D.lineTo((p[2].X + x0), (height - (p[2].Y + y0)));
//                        path2D.lineTo((p[3].X + x0), (height - (p[3].Y + y0)));
//                        path2D.lineTo((p[0].X + x0), (height - (p[0].Y + y0)));
//                        g.draw(path2D);
//                        g.fill(path2D);
//                    }
//                }
//            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RadarVisualizeError(e.getMessage());
        } finally {
            if (g != null) {
                g.dispose();
            }
        }
//        return br;
    }

    //endregion
}
